﻿// This control was originally written by Tomas Herceg and was published
// in the following article: http://www.vbnet.cz/clanek--155.aspx

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Altairis.Web.UI.WebControls {

    /// <summary>
    /// Hierarchická komponenta Repeater
    /// </summary>
    [ParseChildren(true)]
    public class HierarchicalRepeater : HierarchicalDataBoundControl {
        private List<HierarchicalRepeaterDataItem> items = new List<HierarchicalRepeaterDataItem>();

        /// <summary>
        /// Inicializuje novou instanci třídy
        /// </summary>
        public HierarchicalRepeater() {
            this.Items = new ReadOnlyCollection<HierarchicalRepeaterDataItem>(items);
        }

        /// <summary>
        /// Šablona položky s podřízenými položkami
        /// </summary>
        [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty)]
        public ITemplate ItemLayoutTemplate { get; set; }

        /// <summary>
        /// ID komponenty v šabloně ItemLayoutTemplate nebo LeafItemLayoutTemplate, do níž se vloží obsah položky
        /// </summary>
        [Category("Layout"), DefaultValue("ItemPlaceholder"), Bindable(true)]
        public string ItemPlaceholderID {
            get { return (string)ViewState["ItemPlaceholderID"] ?? "ItemPlaceholder"; }
            set { ViewState["ItemPlaceholderID"] = value; }
        }

        /// <summary>
        /// Kolekce vnitřních položek
        /// </summary>
        public ReadOnlyCollection<HierarchicalRepeaterDataItem> Items { get; private set; }

        /// <summary>
        /// Šablona obsahu položky
        /// </summary>
        [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(HierarchicalRepeaterDataItem))]
        public ITemplate ItemTemplate { get; set; }

        /// <summary>
        /// Šablona celé komponenty
        /// </summary>
        [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty)]
        public ITemplate LayoutTemplate { get; set; }

        /// <summary>
        /// Šablona položky bez podřízených položek
        /// </summary>
        [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty)]
        public ITemplate LeafItemLayoutTemplate { get; set; }

        /// <summary>
        /// ID komponenty v šabloně LayoutTemplate, do níž se vloží celý obsah komponenty
        /// </summary>
        [Category("Layout"), DefaultValue("MainPlaceholder"), Bindable(true)]
        public string MainPlaceholderID {
            get { return (string)ViewState["MainPlaceholderID"] ?? "MainPlaceHolder"; }
            set { ViewState["MainPlaceholderID"] = value; }
        }

        /// <summary>
        /// ID komponenty v šabloně ItemLayoutTemplate, do níž se vloží podřízené položky
        /// </summary>
        [Category("Layout"), DefaultValue("SubItemsPlaceholder"), Bindable(true)]
        public string SubItemsPlaceholderID {
            get { return (string)ViewState["SubItemsPlaceholderID"] ?? "SubItemsPlaceholder"; }
            set { ViewState["SubItemsPlaceholderID"] = value; }
        }

        /// <summary>
        /// Vrací HTML tag hlavního elementu komponenty
        /// </summary>
        protected override HtmlTextWriterTag TagKey {
            get { return HtmlTextWriterTag.Div; }
        }

        /// <summary>
        /// Vytáhne z datového zdroje data a vytvoří vnitřní komponenty
        /// </summary>
        protected override void CreateChildControls() {
            if (LeafItemLayoutTemplate == null) throw new ArgumentNullException("LeafItemLayoutTemplate");
            if (ItemLayoutTemplate == null) throw new ArgumentNullException("ItemLayoutTemplate");
            if (ItemTemplate == null) throw new ArgumentNullException("ItemTemplate");
            if (string.IsNullOrEmpty(MainPlaceholderID) && LayoutTemplate != null) throw new ArgumentException("MainPlaceholderID");
            if (string.IsNullOrEmpty(ItemPlaceholderID)) throw new ArgumentException("ItemPlaceholderID");
            if (string.IsNullOrEmpty(SubItemsPlaceholderID)) throw new ArgumentException("SubItemsPlaceholderID");

            // pokud máme LayoutTemplate, instanciovat ji a najít placeholder
            if (LayoutTemplate != null) LayoutTemplate.InstantiateIn(this);
            var mainContainer = this.FindControl(MainPlaceholderID) ?? this;

            // instanciovat jednotlivé položky
            RecursiveCreateChildControls(mainContainer.Controls, GetData("").Select(), 0, -1);
        }

        /// <summary>
        /// Vytvoří položky pro danou kolekci hierarchických dat
        /// </summary>
        private void RecursiveCreateChildControls(ControlCollection container, IHierarchicalEnumerable values, int level, int parentIndex) {
            // projít všechny hodnoty
            foreach (var value in values.OfType<object>().Select(d => values.GetHierarchyData(d))) {
                // vytvořit datovou položku
                var item = new HierarchicalRepeaterDataItem();
                item.DataItemIndex = items.Count;
                item.DataItemParentIndex = parentIndex;
                item.DataItemLevel = level;
                item.DataItem = value.Item;
                item.DataItemPath = value.Path;
                item.DisplayIndex = items.Count;

                // instanciovat šablonu podle toho, jestli položka má potomky
                if (value.HasChildren) {
                    ItemLayoutTemplate.InstantiateIn(item);
                }
                else {
                    LeafItemLayoutTemplate.InstantiateIn(item);
                }

                // přidat datovou položku do kolekce
                container.Add(item);
                items.Add(item);

                // nahradit placeholder s ID ve vlastnosti ItemPlaceholderID
                var control = item.FindControl(ItemPlaceholderID);
                if (control == null) throw new ArgumentException("ItemPlaceholderID");
                ItemTemplate.InstantiateIn(control);

                // provést databinding na položce
                item.DataBind();

                // vytvořit vnitřní položky
                if (value.HasChildren) {
                    control = item.FindControl(SubItemsPlaceholderID);
                    if (control == null) throw new ArgumentException("SubItemPlaceholderID");
                    RecursiveCreateChildControls(control.Controls, value.GetChildren(), level + 1, item.DataItemIndex);
                }
            }
        }

        /// <summary>
        /// Položka komponenty HierarchyRepeater
        /// </summary>
        public class HierarchicalRepeaterDataItem : Control, INamingContainer, IDataItemContainer {

            /// <summary>
            /// Vrátí datový objekt, proti kterému se provádí databinding
            /// </summary>
            public object DataItem { get; internal set; }

            /// <summary>
            /// Vrací index položky v kolekci položek rodičovské komponenty
            /// </summary>
            public int DataItemIndex { get; internal set; }

            /// <summary>
            /// Vrací úroveň (číslováno od 0) položky v hierarchii
            /// </summary>
            public int DataItemLevel { get; internal set; }

            /// <summary>
            /// Vrací index rodičovské položky v hierarchii
            /// </summary>
            public int DataItemParentIndex { get; internal set; }

            /// <summary>
            /// Vrací cestu položky v hierarchii
            /// </summary>
            public string DataItemPath { get; internal set; }

            /// <summary>
            /// Vrací pozici položky zobrazené v komponentě
            /// </summary>
            public int DisplayIndex { get; internal set; }
        }
    }
}